<?php
/**
 *+------------------
 * Lflow
 *+------------------
 * Copyright (c) 2023~2030 gitee.com/liu_guan_qing All rights reserved.本版权不可删除，侵权必究
 *+------------------
 * Author: Mr.April(405784684@qq.com)
 *+------------------
 */

namespace lflow\core\services;

use lflow\assign\Assignment;
use lflow\ckpt\ProcessCkpt;
use lflow\ckpt\TaskCkpt;
use lflow\core\BaseServices;
use lflow\core\Execution;
use lflow\core\QueryServices;
use lflow\dao\TaskDao;
use lflow\exceptions\WorkFlowException;
use lflow\impl\GeneralAccessStrategy;
use lflow\lib\util\AssertHelper;
use lflow\lib\util\DateHelper;
use lflow\lib\util\ObjectHelper;
use lflow\lib\util\StringHelper;
use lflow\model\HistoryTaskActorModel;
use lflow\model\HistoryTaskModel;
use lflow\model\TaskActorModel;
use lflow\model\TaskModel;
use PhpEnum\Exceptions\CloneNotSupportedException;
use ReflectionClass;

class TaskServices extends BaseServices
{
    const START = 'workflow::start';
    const ADMIN = "lflow.admin";
    const AUTO = "lflow.auto";
    const ID = "lflow.orderNo";

    /**
     * @param \lflow\dao\TaskDao $dao
     */
    public function __construct(TaskDao $dao)
    {
        $this->dao = $dao;
    }

    /**
     * 实现类创建task，并根据ckpt模型类型决定是否分配参与者
     *
     * @param \lflow\ckpt\TaskCkpt  $taskCkpt  模型
     * @param \lflow\core\Execution $execution 执行对象
     *
     * @return array
     */
    public function createTask(TaskCkpt $taskCkpt, Execution $execution): array
    {
        $tasks = [];
        $args  = $execution->getArgs();
        if ($args == null) {
            $args = new \stdClass();
        }
        $expireDate = DateHelper::processTime($args, $taskCkpt->getExpireTime());
        $remindDate = DateHelper::processTime($args, $taskCkpt->getReminderTime());
        $form       = '';
        $actionUrl  = StringHelper::isEmpty($form) ? $taskCkpt->getForm() : $form;//表单数据
        $actors     = $this->getTaskActors($taskCkpt, $execution);
        $taskModel  = $this->createTaskBase($taskCkpt, $execution);
        $args       = ObjectHelper::put($args, TaskModel::KEY_ACTOR, implode(',', $actors));
        $taskModel->set('action_url', $actionUrl);
        $taskModel->set('expire_date', $expireDate);
        $taskModel->set('expire_time', $remindDate);
        $taskModel->set('variable', $args);
        if ($taskCkpt->isPerformAny()) {
            //任务执行方式为参与者中任何一个执行即可驱动流程继续流转，该方法只产生一个task
            $taskModel = $this->saveTask($taskModel, $actors);
            $tasks[]   = $taskModel;
        } else if ($taskCkpt->isPerformAll()) {
            //任务执行方式为参与者中每个都要执行完才可驱动流程继续流转，该方法根据参与者个数产生对应的task数量
            $actorIds = is_array($actors) ? $actors : explode(',', $actors);
            foreach ($actorIds as $actor) {
                $singleTask = clone $taskModel;
                $singleTask = $this->saveTask($singleTask, $actor);
                $singleTask->set('remind_date', $remindDate);
                $tasks[] = $singleTask;
            }
        }
        return $tasks;
    }

    private function getTaskActors(TaskCkpt $ckpt, Execution $execution): ?array
    {
        $assigneeObject = null;
        $handler        = $ckpt->getAssignmentHandlerObject();
        $scope          = $ckpt->getScope();
        if ((int)$scope === 0 && strlen($ckpt->getAssignee()) > 0) {
            $assigneeObject = $execution->getArgs()[$ckpt->getAssignee()];
        } else if ($handler != null) {
            if ($handler instanceof Assignment) {
                $assigneeObject = $handler->assign($ckpt, $execution);
            } else {
                $assigneeObject = $handler->assign($execution);
            }
        }
        return $this->getTaskAssigner($assigneeObject == null ? (object)[$ckpt->getAssignee()] : $assigneeObject);
    }

    private function getTaskAssigner(mixed $actors): ?array
    {
        if ($actors === null) {
            return null;
        }
        $results = [];
        if (is_string($actors)) {
            // 如果值为字符串类型，则使用逗号分隔
            return explode(',', $actors);
        } else if (is_array($actors)) {
            // 如果值为数组类型，则直接返回
            return $actors;
        } else if (is_a($actors, '\stdClass')) {
            // 如果值为对象类型，且为List类型，则增加ArrayList的逻辑判断
            $list    = (array)$actors;
            $size    = count($list);
            $results = array_fill(0, $size, '');
            for ($i = 0; $i < $size; $i++) {
                $results[$i] = strval($list[$i]);
            }
            return $results;
        } else if (is_numeric($actors)) {
            // 如果为数字类型，则返回1个元素的String[]
            return ['' . $actors];
        } else {
            // 其它类型，抛出不支持的类型异常
            throw new WorkFlowException("任务参与者对象[$actors]类型不支持。合法参数示例:Long,Integer,new String[]{},'10000,20000',List");
        }
    }

    /**
     * @param \lflow\ckpt\TaskCkpt  $ckpt
     * @param \lflow\core\Execution $execution
     *
     * @return \lflow\model\TaskModel
     */
    public function createTaskBase(TaskCkpt $ckpt, Execution $execution): TaskModel
    {
        $taskModel = new TaskModel();//必须重新new要不执行任务续流只会有第一次的数据
        $taskModel->set('id', StringHelper::getPrimaryKey());
        $taskModel->set('process_id', $execution->getProcess()->getData('id'));
        $taskModel->set('order_id', $execution->getOrder()->getData('id'));
        $taskModel->set('task_name', $ckpt->getName());
        $taskModel->set('display_name', $ckpt->getDisplayName());
        if ($ckpt->isMajor()) {
            $taskModel->set('task_type', array_search('Major', TaskCkpt::$TaskType));
        } else {
            $taskModel->set('task_type', array_search('Aidant', TaskCkpt::$TaskType));
        }
        $taskModel->set('parent_task_id', $execution->getTask() == null ? self::START : $execution->getTask()->getData('id'));
        return $taskModel;
    }

    private function saveTask(TaskModel $taskModel, string|array $actors = []): TaskModel
    {
        $taskModel->set('id', StringHelper::getPrimaryKey());
        $taskModel->set('performType', array_search('ANY', TaskCkpt::$PerformType));
        $taskModel->set('actor_ids', is_array($actors) ? $actors : explode(',', $actors));
        $taskModel->save();
        $this->assignTask($taskModel->getData('id'), $actors);
        return $taskModel;
    }

    private function assignTask(string $taskId, string|array $actorIds): void
    {
        $actorIds = is_array($actorIds) ? $actorIds : explode(',', $actorIds);
        foreach ($actorIds as $actorId) {
            if (StringHelper::isEmpty($actorId)) continue;
            //解决被覆盖问题直接new模型
            $taskActorModel = new TaskActorModel();
            $taskActorModel->set('task_id', $taskId);
            $taskActorModel->set('actor_id', $actorId);
            $taskActorModel->save();
        }
    }

    /**
     * @param string      $taskId
     * @param string|null $operator
     * @param             $args
     *
     * @return \lflow\model\TaskModel
     */
    public function complete(string $taskId, string $operator = null, $args = null): TaskModel
    {

        $task = $this->get($taskId);
        AssertHelper::notNull($task, "指定的任务[id=" . $taskId . "]不存在");
        $variable = $task->getData('variable');
        $args     = ObjectHelper::putAll($variable, $args);
        if (!$this->isAllowed($task, $operator)) {
            throw new WorkFlowException("当前参与者[" . $operator . "]不允许执行任务[taskId=" . $taskId . "]");
        }
        $historyTaskServices = app()->make(HistoryTaskServices::class);
        $historyModle        = $historyTaskServices->historyTask($task);
        $historyModle->set('variable', $args);
        $historyModle->set('finish_time', DateHelper:: getTime());
        $historyModle->set('state', self::STATE_FINISH);
        $historyModle->set('operator', $operator);
        if (empty($historyModle->actor_ids)) {
            $queryServices = app()->make(QueryServices::class);
            $actorIds      = $queryServices->getTaskActorsByTaskId($task->getData('id'));
            $historyModle->set('actor_ids', $actorIds);
        }

        //创建历史记录
        $historyModle->save();
        //创建历史actor记录
        $actorIds = $historyModle->getData('actor_ids') ?? [];
        if (!empty($actorIds)) {
            foreach ($actorIds as $value) {
                $historyActorModel = new HistoryTaskActorModel();
                $actor             = [
                    'task_id'  => $historyModle->getData('id'),
                    'actor_id' => $value,
                ];
                $historyActorModel->save($actor);
            }
        }
        //删除处理人
        $taskActor = app()->make(TaskActorServices::class);
        $taskActor->delete($taskId, 'task_id');
        $task->delete();
        return $task;
    }

    public function isAllowed(TaskModel $task, string $operator): bool
    {
        if (StringHelper::isNotEmpty($operator)) {
            if (StringHelper::equalsIgnoreCase(self::ADMIN, $operator) || StringHelper::equalsIgnoreCase(self::AUTO, $operator)) {
                return true;
            }
            if (StringHelper::isNotEmpty($task->getData('operator'))) {
                return StringHelper::equalsIgnoreCase($task->getData('operator'), $operator);
            }
        }
        $queryServices = app()->make(QueryServices::class);
        $actors        = $queryServices->getTaskActorsByTaskId($task->getData('operator'));
        if (empty($actors)) {
            return true;
        }
        return !StringHelper::isEmpty($operator) && GeneralAccessStrategy::isAllowed($operator, (object)$actors);
    }

    /**
     * 任务历史记录方法
     *
     * @param $execution
     * @param $ckpt
     *
     * @return \lflow\model\HistoryTaskModel
     */
    public function history($execution, $ckpt): HistoryTaskModel
    {
        $historyTask = new HistoryTaskModel();
        $historyTask->set('id', StringHelper::getPrimaryKey());
        $historyTask->set('order_id', $execution->getOrder()->getData('id'));
        $historyTask->set('create_time', DateHelper::getTime());
        $historyTask->set('finish_time', DateHelper::getTime());
        $historyTask->set('display_name', $ckpt->getDisplayName());
        $historyTask->set('task_name', $ckpt->getName());
        $historyTask->set('task_state', self::STATE_FINISH);
        $historyTask->set('task_type', array_search('Record', TaskCkpt::$TaskType));
        $historyTask->set('parent_task_id', $execution->getTask() == null ? 'start' : $execution->getTask()->getData('id'));
        $historyTask->set('variable', $execution->getArgs());
        $historyTask->save();
        return $historyTask;
    }

    /**
     * 提取指定任务，设置完成时间及操作人，状态不改变
     *
     * @param string $taskId
     * @param string $operator
     *
     * @return \lflow\model\TaskModel
     */
    public function take(string $taskId, string $operator): TaskModel
    {
        $task = $this->get($taskId);
        AssertHelper::notNull($task, "指定的任务[id=$taskId]不存在");
        if (!$this->isAllowed($task, $operator)) {
            throw new WorkFlowException("当前参与者[$operator]不允许提取任务[taskId=$taskId]");
        }
        $task->set('operator', $operator);
        $task->set('finish_time', DateHelper::getTime());
        $task->save();
        return $task;
    }

    /**
     * 唤醒指定的历史任务
     *
     * @param string $taskId
     * @param string $operator
     *
     * @return \lflow\model\TaskModel
     */
    public function resume(string $taskId, string $operator): TaskModel
    {
        $historyTaskServices = app()->make(HistoryTaskServices::class);
        $histTask            = $historyTaskServices->dao->get($taskId);
        AssertHelper::notNull($histTask, "指定的历史任务[id=$taskId]不存在");
        $isAllowed = true;
        if (!empty($histTask->getOperator())) {
            $isAllowed = StringHelper::equalsIgnoreCase($histTask->getData('operator'), $histTask);
        }
        if ($isAllowed) {
            $task = $histTask->undoTask($histTask);
            $task->set('id', StringHelper::getPrimaryKey());
            $task->set('create_time', DateHelper::getTime());
            $this->saveTask($task);
            $this->assignTask($task->getData('id'), $task->getData('operator'));
            return $task;
        } else {
            throw new WorkFlowException("当前参与者[$operator]不允许唤醒历史任务[taskId=$taskId]");
        }
    }

    /**
     * 向指定任务移除参与者
     *
     * @param string $taskId
     * @param string $actors
     */
    public function removeTaskActor(string $taskId, string $actors)
    {
        $task = $this->get($taskId);
        AssertHelper::notNull($task, "指定的任务[id=$taskId]不存在");
        if (empty($actors)) return;
        if ($task->isMajor()) {
            $taskActorServices = app()->make(TaskActorServices::class);
            $taskActorServices->removeTaskActor($task->getData('id'), $actors);
            $taskData = $task->getData('variable');
            $actorStr = ObjectHelper::getObjectValue($taskData, TaskModel::KEY_ACTOR);
            if (!empty($actorStr)) {
                $actorArray = explode(',', $actorStr);
                $newActor   = implode(',', array_diff($actorArray, [$actors]));//返回移除后的值
                $taskData   = ObjectHelper::set($taskData, TaskModel::KEY_ACTOR, $newActor);
                $task->set('variable', $taskData);
                $task->save();
            }
        }
    }

    /**
     * 撤回指定任务
     *
     * @throws \think\db\exception\DataNotFoundException
     * @throws \ReflectionException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\db\exception\DbException
     */
    public function withdrawTask(string $taskId, string $operator)
    {
        $historyTaskServices = app()->make(HistoryTaskServices::class);
        $history             = $historyTaskServices->get($taskId);
        AssertHelper::notNull($history, "指定的历史任务[id=$taskId]不存在");
        $tasks = [];
        if ($history->isPerformAny()) {
            $this->dao->selectList(['id' => $history->getData('id')], '*', 0, 0, '', [], true);
        } else {
            $where = [
                'id'             => $history->getData('id'),
                'task_name'      => $history->getData('task_name'),
                'parent_task_id' => getData('parent_task_id'),
            ];
            $tasks = $this->dao->selectList($where, '*', 0, 0, '', [], true);
        }
        if (empty($tasks)) {
            throw new WorkFlowException("后续活动任务已完成或不存在，无法撤回.");
        }
        foreach ($tasks as $task) {
            $task->delete($task);
        }

        $task = $historyTaskServices->undoTask($history);
        $task->set('id', StringHelper::getPrimaryKey());
        $task->set('create_time', DateHelper::getTime());
        $this->saveTask($task);
        $this->assignTask($task->getData('id'), $task->getData('operator'));
        return $task;
    }

    /**
     * 驳回任务
     *
     * @param \lflow\ckpt\ProcessCkpt $ckpt
     * @param \lflow\model\TaskModel  $currentTask
     *
     * @return \lflow\model\TaskModel
     */
    public function rejectTask(ProcessCkpt $ckpt, TaskModel $currentTask): TaskModel
    {
        $parentTaskId = $currentTask->getData('parent_task_id');
        if (StringHelper::isEmpty($parentTaskId)) {
            throw new WorkFlowException("上一步任务ID为空，无法驳回至上一步处理");
        }
        $current             = $ckpt->getNode($currentTask->getData('task_name'));
        $historyTaskServices = app()->make(HistoryTaskServices::class);
        $history             = $historyTaskServices->get($parentTaskId);
        $parent              = $ckpt->getNode($history->getData('task_name'));
        if (!(new TaskCkpt())->canRejected($current, $parent)) {
            throw new WorkFlowException("无法驳回至上一步处理，请确认上一步骤并非fork、join、suprocess以及会签任务");
        }
        $task = $historyTaskServices->undoTask($history);
        $task->set('id', StringHelper:: getPrimaryKey());
        $task->set('create_time', DateHelper::getTime());
        $task->set('operator', $history->getData('operator'));
        $this->saveTask($task);
        $this->assignTask($task->getData('id'), $task->getData('operator'));
        return $task;
    }

    /**
     * 添加处理人
     *
     * @param $taskId
     * @param $performType
     * @param $actors
     */
    public function addTaskActor($taskId, $performType, $actors)
    {
        $task = $this->get($taskId);
        AssertHelper::notNull($task, "指定的任务[id=$taskId]不存在");
        if (!$task->isMajor()) return;
        if (empty($performType)) $performType = $task->getData('perform_type');
        if (empty($performType)) $performType = 0;
        switch ($performType) {
            case 0:
                //添加审批人
                $this->assignTask($task->getData('id'), $actors);
                $data                         = $task->getData('variable');
                $oldActor                     = ObjectHelper::getObjectValue($data, TaskModel::KEY_ACTOR);
                $data->{TaskModel::KEY_ACTOR} = implode(',', array_merge(explode(',', $oldActor), explode(',', $actors)));
                $task->set('variable', $data);
                $task->save();
                break;
            case 1:
                foreach (explode(',', $actors) as $actor) {
                    try {
                        $newTask = clone $task;
                        $newTask->set('id', StringHelper::getPrimaryKey());
                        $newTask->set('create_time', DateHelper::getTime());
                        $newTask->set('operator', $actor);
                        $taskData = $task->getData('variable');
                        $taskData = ObjectHelper::set($taskData, TaskModel::KEY_ACTOR, $actor);
                        $task->set('variable', $taskData);
                        $this->saveTask($newTask);
                        $this->assignTask($newTask->getData('id'), $actor);
                    } catch (CloneNotSupportedException $ex) {
                        throw new WorkFlowException("任务对象不支持复制", $ex->getMessage());
                    }
                }
                break;
            default :
                break;
        }
    }

}
